﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Emil.GMP;
using System.Security.Cryptography;
using NUnit.Framework;

namespace BYU_ECC
{
	/// <summary>
	/// Encryption algorithm utilizing ECDH, or Elliptic Curve Diffie-Hellman, public and private keys
	/// </summary>
    class Encoder
    {
        #region Encoder Members and Properties
        private static int chunkSize;
        public const int CharSize = 8;

		private static String originalMessage;
		public static String OriginalMessage {
			get {
				CheckGenerated();
				return originalMessage;
			}
		}

		private static ArrayList encodedMessages;
		public static EncodedMessage[] EncodedMessages {
			get {
				CheckGenerated();
				return (EncodedMessage[])encodedMessages.ToArray(typeof(EncodedMessage));
			}
		}

        #endregion

        #region Singleton Instance Functions
		private static Encoder instance;
		public Encoder Instance() {
            CheckGenerated();
            return instance;
        }
		public static void GenerateEncoder(string message) {
			instance = new Encoder(message);
        }
		public static void GenerateEncoder(Point point) {
			instance = new Encoder(point);
		}
		private Encoder(Point point) {
            // Set chunk size
            SetChunkSize();

            // Convert point to string for testing purposes
			originalMessage = point.ToString();
			Encoder.encodedMessages = new ArrayList();
			
			// Encode point only
			EncodeChunk(point);
		}
        private Encoder(string message) {
            // Set chunk size
            SetChunkSize();

            // Set up variables and encode
			originalMessage = message;
			Encoder.encodedMessages = new ArrayList();
			Encode();
        }
        private void SetChunkSize() {
            chunkSize = (int)ParameterGenerator.KeySize / CharSize;
        }
		private void Encode() {
			// Split message into chunks
			int curChar = 0;
			while (curChar < originalMessage.Length) {
				// Find correct length of chunk
				int len = chunkSize;
				if ((curChar+len)>originalMessage.Length)
					len = originalMessage.Length - curChar;

				// Split into chunk and encode point
                string str = originalMessage.Substring(curChar, len);
				EncodeChunk(Point.ConvertToPoint(str));

				// Increment curChar
				curChar += len;
			}
		}
		/// <summary>
		/// Uses the properties of ECDH (Diffie-Hellman) to encode a point on the curve using a secret key.
		/// </summary>
		/// <param name="p">A Point representing strings</param>
		private void EncodeChunk(Point p) {
			// Generate random k
			BigInt k = ParameterGenerator.GenerateRandomBigInt(ParameterGenerator.N);

			// Generate encoded message
			EncodedMessage message = new EncodedMessage(
				k*ParameterGenerator.BasePoint, 
				p + (k * KeyPairs.PublicKeyB));

			// Add message to list
			encodedMessages.Add(message);
		}
		#endregion

		private static void CheckGenerated()
        {
            if (instance == null)
				throw new EncoderNotGeneratedException();
        }
    }

	class EncodedMessage {
		#region EncodedMessage members

		private Point chosenPoint;
		public Point ChosenPoint {
			get { return chosenPoint; }
		}

		private Point encodedPoint;
		public Point EncodedPoint {
			get { return encodedPoint; }
		}

		#endregion
		
		public EncodedMessage(Point chosenPoint, Point encodedPoint) {
			this.chosenPoint = chosenPoint;
			this.encodedPoint = encodedPoint;
		}

		public override string ToString() {
			return "[ChosenPoint: " + chosenPoint + ", EncodedPoint: " + encodedPoint + "]";
		}
	}

	class EncoderNotGeneratedException : Exception { }

    #region Encoder Tests

    [TestFixture]
	class EncoderTest {
		[SetUp]
		public void SetUp() {
			ParameterGenerator.GenerateTestParameters(KeySizeEnum.Size192);
			KeyPairs.GenerateKeyPairs();
		}

		[TearDown]
		public void TearDown() {

		}

		[Test]
		public void TestChunking() {
			// See if we have the right number of chunks for the string we are encoding
			Encoder.GenerateEncoder("hello");
            Assert.IsTrue(Encoder.EncodedMessages.Length == 1);
            Encoder.GenerateEncoder(BigInt.Two.Power(192 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 2);
            Encoder.GenerateEncoder(BigInt.Two.Power(384 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 3);

            // See if we have the right number of chunks for the string we are encoding
            ParameterGenerator.GenerateTestParameters(KeySizeEnum.Size224);
            Encoder.GenerateEncoder("hello");
            Assert.IsTrue(Encoder.EncodedMessages.Length == 1);
            Encoder.GenerateEncoder(BigInt.Two.Power(224 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 2);
            Encoder.GenerateEncoder(BigInt.Two.Power(448 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 3);

            // See if we have the right number of chunks for the string we are encoding
            ParameterGenerator.GenerateTestParameters(KeySizeEnum.Size256);
            Encoder.GenerateEncoder("hello");
            Assert.IsTrue(Encoder.EncodedMessages.Length == 1);
            Encoder.GenerateEncoder(BigInt.Two.Power(256 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 2);
            Encoder.GenerateEncoder(BigInt.Two.Power(512 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 3);

            // See if we have the right number of chunks for the string we are encoding
            ParameterGenerator.GenerateTestParameters(KeySizeEnum.Size384);
            Encoder.GenerateEncoder("hello");
            Assert.IsTrue(Encoder.EncodedMessages.Length == 1);
            Encoder.GenerateEncoder(BigInt.Two.Power(384 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 2);
            Encoder.GenerateEncoder(BigInt.Two.Power(768 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 3);

            // See if we have the right number of chunks for the string we are encoding
            ParameterGenerator.GenerateTestParameters(KeySizeEnum.Size521);
            Encoder.GenerateEncoder("hello");
            Assert.IsTrue(Encoder.EncodedMessages.Length == 1);
            Encoder.GenerateEncoder(BigInt.Two.Power(521 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 2);
            Encoder.GenerateEncoder(BigInt.Two.Power(1042 / Encoder.CharSize).ToString(2));
            Assert.IsTrue(Encoder.EncodedMessages.Length == 3);
		}

		[Test]
		public void TestEncoder() {
			Encoder.GenerateEncoder("hello");
			Assert.IsTrue(Encoder.EncodedMessages.Length == 1);
			Console.WriteLine(Encoder.EncodedMessages[0]);
		}

    }

    #endregion
}

